1   /*
2    * Copyright (C) 2012 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.testing;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  
21  import com.google.common.collect.ImmutableList;
22  import com.google.common.collect.Sets;
23  import com.google.common.reflect.AbstractInvocationHandler;
24  import com.google.common.reflect.Invokable;
25  import com.google.common.reflect.Parameter;
26  import com.google.common.reflect.TypeToken;
27  
28  import java.io.Serializable;
29  import java.lang.reflect.Method;
30  import java.lang.reflect.Proxy;
31  import java.util.Set;
32  
33  import javax.annotation.Nullable;
34  
35  /**
36   * Generates a dummy interface proxy that simply returns a dummy value for each method.
37   *
38   * @author Ben Yu
39   */
40  abstract class DummyProxy {
41  
42    /**
43     * Returns a new proxy for {@code interfaceType}. Proxies of the same interface are equal to each
44     * other if the {@link DummyProxy} instance that created the proxies are equal.
45     */
46    final <T> T newProxy(TypeToken<T> interfaceType) {
47      Set<Class<?>> interfaceClasses = Sets.newLinkedHashSet();
48      interfaceClasses.addAll(interfaceType.getTypes().interfaces().rawTypes());
49      // Make the proxy serializable to work with SerializableTester
50      interfaceClasses.add(Serializable.class);
51      Object dummy = Proxy.newProxyInstance(
52          interfaceClasses.iterator().next().getClassLoader(),
53          interfaceClasses.toArray(new Class<?>[interfaceClasses.size()]),
54          new DummyHandler(interfaceType));
55      @SuppressWarnings("unchecked") // interfaceType is T
56      T result = (T) dummy;
57      return result;
58    }
59  
60    /** Returns the dummy return value for {@code returnType}. */
61    abstract <R> R dummyReturnValue(TypeToken<R> returnType);
62  
63    private class DummyHandler extends AbstractInvocationHandler implements Serializable {
64      private final TypeToken<?> interfaceType;
65  
66      DummyHandler(TypeToken<?> interfaceType) {
67        this.interfaceType = interfaceType;
68      }
69    
70      @Override protected Object handleInvocation(
71          Object proxy, Method method, Object[] args) {
72        Invokable<?, ?> invokable = interfaceType.method(method);
73        ImmutableList<Parameter> params = invokable.getParameters();
74        for (int i = 0; i < args.length; i++) {
75          Parameter param = params.get(i);
76          if (!param.isAnnotationPresent(Nullable.class)) {
77            checkNotNull(args[i]);
78          }
79        }
80        return dummyReturnValue(interfaceType.resolveType(method.getGenericReturnType()));
81      }
82  
83      @Override public int hashCode() {
84        return identity().hashCode();
85      }
86  
87      @Override public boolean equals(Object obj) {
88        if (obj instanceof DummyHandler) {
89          DummyHandler that = (DummyHandler) obj;
90          return identity().equals(that.identity());
91        } else {
92          return false;
93        }
94      }
95  
96      private DummyProxy identity() {
97        return DummyProxy.this;
98      }
99  
100     @Override public String toString() {
101       return "Dummy proxy for " + interfaceType;
102     }
103 
104     // Since type variables aren't serializable, reduce the type down to raw type before
105     // serialization.
106     private Object writeReplace() {
107       return new DummyHandler(TypeToken.of(interfaceType.getRawType()));
108     }
109   }
110 }